# koroFileHeader at Yungoal acer
# Create: 2022-10-10 08:49:42
# LastEdit: 2023-01-13 09:25:38
"""报表同步"""
__author__ = '749B'

if __name__ == '__main__':
    # 导入路径
    from pathlib import Path
    BASE_DIR = Path(__file__).resolve().parent.parent
    import sys
    sys.path.append(BASE_DIR.__str__())
    # Django 初始化
    import django
    from django.conf import settings
    from azure_ea_insights import settings as my_settings
    settings.configure(**{ k: v for k, v in vars(my_settings).items() if k.isupper() })
    django.setup()

import logging
import time
import datetime
from dateutil.relativedelta import relativedelta
from django.utils import timezone

from billing import models
from utils.azure_ea import AzureBillingClient
from utils.vault import decrypt

from tasks.thread_task import BaseTask

from typing import Optional


def get_client_from_first_record(password: Optional[str] = None) -> AzureBillingClient:
    """从数据库中获取第一个账号的信息"""
    account = models.Account.objects.first()
    assert account
    enrollment = account.enrollment
    # 由于settings配置在 if __name__ == '__main__' 中，
    # 下面使用的settings.VAULT_PASSWORD，在其他模块中引用这个方法的时候无法获取到settings
    # 需要在调用函数时自己传入password参数
    access_key = decrypt(account.access_key, password or settings.VAULT_PASSWORD)
    assert access_key
    client = AzureBillingClient(enrollment, access_key, name=account.name)
    return client

    
class ReportSync(BaseTask):
    
    def __init__(
        self, 
        client: AzureBillingClient, 
        from_latest_date: bool = True,  # 增量(按月)同步，从记录中最新的月份开始
        last_months: int = 0,           # 最近的几个月，或者0表示全要
        ) -> None:
        """
        增量更新 from_latest_date=True:
            如果Detail表这已经有记录了 则忽略 last_months
            如果Detail表无无记录 则根据 last_months 更新最早的记录
        全量更新 from_latest_date=Fals:
            每次都从 last_months 的位置开始检查更新
            如果 last_months=0 则从最早的位置开始检查更新
        如果已有近期的记录，需要补齐更早的历史记录，需要把增量关掉

        最近的几个月 last_months:
            0 表示全要
            1 表示倒推1个月 这实际会更新当月和上月两个月的记录
            2... 以此类推
        """
        assert last_months >= 0
        self.client = client
        super().__init__(task_name=self.client.name)
        self.start_month = self.get_start_month(from_latest_date, last_months)

        self.logger = logging.getLogger("tasks.ReportSync")
        self.logger.success = lambda msg, *args, **kwargs: self.logger.log(25, msg, *args, **kwargs)  # type: ignore

    @staticmethod
    def get_start_month(from_latest_date: bool = True, last_months: int = 0) -> Optional[datetime.date]:
        """从哪个月的数据开始同步"""
        start_month = None
        if from_latest_date:
            try:
                start_month = models.Detail.objects.latest('date').date.replace(day=1)
            except models.Detail.DoesNotExist:
                pass
        if start_month is None and last_months:
            # 当前日期倒推几个月的1号的日期
            start_month = timezone.localdate() - relativedelta(months=last_months, day=1)
        return start_month

    @BaseTask.set_task_info
    def run(self):
        self.logger.info('同步报表任务开始: %s', self.task_key[1])
        available_months = self.client.get_available_months()
        for month in available_months:
            if self.start_month and self.start_month > month:
                continue
            summary = self.summary_report(month)
            self.detail_report(month, summary)
            self.price_sheet_report(month, summary)
        self.logger.info('同步报表任务完成: %s', self.task_key[1])

    def summary_report(self, month: datetime.date):
        """同步报表 Summary"""
        month_str = month.strftime('%Y-%m') 
        self.logger.info('同步报表: Summary(%s)', month_str)

        report_type = 'summary'
        fmt = 'json'
        resp = self.client.get_usage_by_month(month_str, report_type, fmt)
        resp_obj = resp.json()
        resp_dict = { i['Azure Service Commitment']: i['Amount'] for i in resp_obj if i['Azure Service Commitment'] }
        defaults = models.Summary.dict_from_api(resp_dict)
        obj, created = models.Summary.objects.get_or_create(
            report_month=month,
            defaults=defaults,
            )
        if created:
            self.logger.success('创建报表: Summary(%s)', month_str)  # type: ignore
            return obj
        else:
            new_report_data = defaults['report_generation_date']
            assert isinstance(new_report_data, datetime.date)
            old_report_data = obj.report_generation_date
            if new_report_data > old_report_data:
                for k, v in defaults.items():
                    setattr(obj, k, v)
                obj.save()
                self.logger.success(  # type: ignore
                    '更新报表: Summary(%s), %s > %s', 
                    month_str, old_report_data, new_report_data)
                return obj
        self.logger.debug('报表无更新: Summary(%s) 报表生成日期: %s', month_str, obj.report_generation_date)
        return obj

    def detail_report(self, month: datetime.date, summary: models.Summary, check_summary: bool=True):
        """同步报表 Detail
        check_summary: 默认情况下是检查Summary报表并按需操作。设置为False则强制执行
        """
        month_str = month.strftime('%Y-%m') 
        if check_summary and summary.detail_generation_date == summary.report_generation_date:
            self.logger.debug('报表无更新: Detail(%s) Detail表生成日期: %s', month_str, summary.detail_generation_date)
            return
        self.logger.info('同步报表: Detail(%s)', month_str)

        indentation = " " * 4
        start_time= {}
        start_time["request"] = time.time()
        report_type = 'detail'
        fmt = 'json'
        r = self.client.get_usage_by_month(month_str, report_type, fmt)
        resp = r.json()
        print(f'{indentation}请求Detail报表: {month_str} {time.time() - start_time["request"]:.2f}秒')
        start_time["load"] = time.time()
        new_objs = []
        for row in resp:
            sub, created = models.Subscriptions.objects.get_or_create(
                guid=row['SubscriptionGuid'],
                defaults={
                    'short_id': row['SubscriptionId'],
                    'name': row['SubscriptionName'],
                }
            )
            # 订阅名称是可以被修改的，发现后更新为最新的订阅名称
            if not created and sub.name != row['SubscriptionName']:
                sub.name = row['SubscriptionName']
                sub.save
            defaults = models.Detail.dict_from_api(row, sub)
            new_objs.append(models.Detail(**defaults))
            print(
                f'\r{indentation}载入Detail报表进度: {month_str} {len(new_objs)}/{len(resp)} {time.time() - start_time["load"]:.2f}秒', 
                end='', flush=True)
        print()
        if new_objs:
            start_time["bulk"] = time.time()
            models.Detail.objects.filter(date__year=month.year, date__month=month.month).delete()
            models.Detail.objects.bulk_create(new_objs)
            self.logger.success(  # type: ignore
                '批量录入Detail报表: %s %d %.2f秒', 
                month_str, len(resp), time.time() - start_time["bulk"])
        summary.detail_generation_date = summary.report_generation_date
        summary.save()
        self.logger.info('同步报表完成: Detail(%s) %.2f秒', month_str, time.time() - start_time['request'])

    def price_sheet_report(self, month: datetime.date, summary: models.Summary, check_summary: bool=True):
        """同步报表 PriceSheet
        check_summary: 默认情况下是检查Summary报表并按需操作的。设置为False则强制执行
        """
        month_str = month.strftime('%Y-%m') 
        if check_summary and summary.has_price_sheet:
            self.logger.debug('报表无更新: PriceSheet(%s) 已有该月表格', month_str)
            return
        self.logger.info('同步报表: PriceSheet(%s)', month_str)

        indentation = " " * 4
        start_time= {}
        start_time["request"] = time.time()
        report_type = 'pricesheet'
        fmt = 'json'
        r = self.client.get_usage_by_month(month_str, report_type, fmt)
        resp = r.json()
        print(f'{indentation}请求PriceSheet报表: {month_str} {time.time() - start_time["request"]:.2f}秒')
        start_time["load"] = time.time()
        new_objs = []
        for row in resp:
            defaults = models.PriceSheet.dict_from_api(row, month)
            new_objs.append(models.PriceSheet(**defaults))
            print(
                f'\r{indentation}载入PriceSheet报表进度: {month_str} {len(new_objs)}/{len(resp)} {time.time() - start_time["load"]:.2f}秒', 
                end='', flush=True)
        print()
        if new_objs:
            start_time["bulk"] = time.time()
            models.PriceSheet.objects.filter(report_month=month).delete()
            models.PriceSheet.objects.bulk_create(new_objs)
            self.logger.success(  # type: ignore
                '批量录入PriceSheet报表: %s %d %.2f',
                month_str, len(resp), time.time() - start_time["bulk"])
        summary.has_price_sheet = True
        summary.save()
        self.logger.info('同步报表完成: PriceSheet(%s) %.2f秒', month_str, time.time() - start_time['request'])


if __name__ == '__main__':
    client = get_client_from_first_record()
    task = ReportSync(client)
    started = task.start()
    if started:
        print('同步报表开始')
    else:
        print('任务未开始', task.task_start_msg)

